package com.chen.shopping_seckill_service.service;

import cn.hutool.bloomfilter.BitMapBloomFilter;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chen.shopping_common.pojo.CartGoods;
import com.chen.shopping_common.pojo.Orders;
import com.chen.shopping_common.pojo.SeckillGoods;
import com.chen.shopping_common.result.BusException;
import com.chen.shopping_common.result.CodeEnum;
import com.chen.shopping_common.service.OrderService;
import com.chen.shopping_common.service.SeckillService;
import com.chen.shopping_seckill_service.mapper.SeckillGoodsMapper;
import com.chen.shopping_seckill_service.redis.RedissonLock;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.SimpleTimeZone;
import java.util.concurrent.TimeUnit;

@DubboService
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private BitMapBloomFilter bloomFilter;

    @Autowired
    private RedissonLock redissonLock;
    /**
     * 定时同步Mysql中的秒杀商品到Redis中
     */
    @Scheduled(cron = "0/5 * * * * *")
    public void refreshSeckillGoods(){
        System.out.println("同步到redis中");

        /**
         * 在同步到redis之前先将redis修改好的数据存到mysql中
         */
        List<SeckillGoods> seckillGoods1 = redisTemplate.boundHashOps("seckillGoods").values();
        for (SeckillGoods seckillGoods : seckillGoods1) {
            QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("goodsId",seckillGoods.getGoodsId());
            SeckillGoods seckillGoods2 = seckillGoodsMapper.selectOne(queryWrapper);
            if (seckillGoods2!=null){
                seckillGoods2.setStockCount(seckillGoods.getStockCount());
                seckillGoodsMapper.updateById(seckillGoods2);
            }

        }
        /**
         * 查询可以售卖的秒杀商品
         */
        QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
        Date date = new Date();
        String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
        queryWrapper.le("startTime",now)  //晚于开始时间
                .ge("endTime",now)  //早于结束时间
                .gt("stockCount",0); //库存大于零
        List<SeckillGoods> seckillGoods = seckillGoodsMapper.selectList(queryWrapper);
        /**
         * 删除之前的秒杀商品
         */
        redisTemplate.delete("seckillGoods");
        /**
         * 保存到Redis中
         */
        for (SeckillGoods seckillGood : seckillGoods) {
            redisTemplate.boundHashOps("seckillGoods").put(seckillGood.getGoodsId(),seckillGood);
            //布隆过滤器中只存储秒杀商品的商品id
            bloomFilter.add(seckillGood.getGoodsId().toString());
        }

    }

    /**
     * 从redis中分页查询秒杀商品
     * @param page
     * @param size
     * @return
     */
    @SentinelResource(value = "findPageByRedis")
    @Override
    public Page<SeckillGoods> findPageByRedis( int page, int size) throws InterruptedException {
        //查询所有秒杀商品列表
        List<SeckillGoods> seckillGoods = redisTemplate.boundHashOps("seckillGoods").values();
        //获取当前页列表
        int start = (page - 1) * size;
        int end = start + size > seckillGoods.size() ? seckillGoods.size() : start + size;
        List<SeckillGoods> seckillGoods1 = seckillGoods.subList(start, end);
        //构建分页对象
        Page<SeckillGoods> page1 = new Page<>();
        page1.setSize(size) //每页条数
                .setTotal(seckillGoods.size())  //总条数
                .setCurrent(page)  //当前页
                .setRecords(seckillGoods1);  //结果集
//        Thread.sleep(600);
        return page1;
    }

    /**
     * 从Redis中查询秒杀商品详情
     * @param goodsId
     * @return
     */
    @Override
    public SeckillGoods findSeckillGoodsByRedis(Long goodsId) {
        if (!bloomFilter.contains(goodsId.toString())){
            System.out.println("布隆过滤器不存在该数据");
            return null;
        }

        SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps("seckillGoods").get(goodsId);
        if (seckillGoods != null){
            System.out.println("从Redis中查询秒杀商品");
            return seckillGoods;
        }
        return null;
    }

    @Override
    public Orders addSeckillGoodsOrder(Orders orders) {
        String lockKey = orders.getCartGoods().get(0).getGoodId().toString();
        if (redissonLock.lock(lockKey,10000)){
            try{
                //生成订单对象
                orders.setId(IdWorker.getIdStr()); //雪花算法手动生成id
                orders.setStatus(1);
                orders.setCreateTime(new Date());
                orders.setExpire(new Date(new Date().getTime()+1000*60*5));
                //计算商品价格
                CartGoods cartGoods = orders.getCartGoods().get(0);
                Integer num = cartGoods.getNum();
                BigDecimal price = cartGoods.getPrice();
                BigDecimal sum = price.multiply(BigDecimal.valueOf(num));
                orders.setPayment(sum);
                //查询秒杀商品
                Long goodId = cartGoods.getGoodId();
                SeckillGoods seckillGoodsByRedis = findSeckillGoodsByRedis(goodId);
                Integer stockCount = seckillGoodsByRedis.getStockCount();
                if (stockCount <= 0){
                    throw new BusException(CodeEnum.NO_STOCK_ERROR);
                }
                //减少库存
                seckillGoodsByRedis.setStockCount(seckillGoodsByRedis.getStockCount() - cartGoods.getNum());
                redisTemplate.boundHashOps("seckillGoods").put(seckillGoodsByRedis.getGoodsId(),seckillGoodsByRedis);
                //保存商品
                /**
                 * 若redis中键值对类型数据的键是String类型时，一定要给键设置解码器，否则无法存储
                 */
                redisTemplate.setKeySerializer(new StringRedisSerializer());
                redisTemplate.opsForValue().set(orders.getId(),orders,100, TimeUnit.MINUTES);
                /**
                 * 创建秒杀订单副本，以便回退库存时获取订单信息
                 */
                redisTemplate.opsForValue().set(orders.getId()+"_copy",orders,102,TimeUnit.MINUTES);
                System.out.println("下单成功！库存还剩"+seckillGoodsByRedis.getStockCount()+"件");
                return orders;
            }finally {
                redissonLock.unlock(lockKey);
            }
        }else{
            return null;
        }

    }

    @Override
    public Orders findByOrderId(String orderId) {
        return (Orders) redisTemplate.opsForValue().get(orderId);
    }

    /**
     * 支付秒杀订单
     * @param orderId
     * @return
     */
    @Override
    public Orders pay(String orderId) {
        //获取订单
        Orders orders = findByOrderId(orderId);
        if (orders == null){
            throw new BusException(CodeEnum.SECKILL_ORDER_PAYMENT_ERROR);
        }
        //设置订单支付相关参数
        orders.setStatus(2);
        orders.setPaymentType(2);
        orders.setPaymentTime(new Date());
        //删除redis中的订单数据
        redisTemplate.delete(orderId);
        redisTemplate.delete(orderId+"_copy");
        //返回订单
        return orders;
    }

    @Override
    public void addSeckillGoodsToRedis(SeckillGoods seckillGoods) {
        redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getGoodsId(),seckillGoods);
        bloomFilter.add(seckillGoods.getGoodsId().toString());
    }

    //从数据库中查询秒杀商品详情

    /**
     * 对访问数据库的方法进行流量监视
     * @param goodsId
     * @return
     */
    @SentinelResource(value = "findSeckillGoodsByMysql" , blockHandler = "mySqlBlockHandler")
    @Override
    public SeckillGoods findSeckillGoodsByMysql(Long goodsId) {
        System.out.println("从数据库中查询秒杀商品");
        QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("goodsId",goodsId);
        SeckillGoods seckillGoods1 = seckillGoodsMapper.selectOne(queryWrapper);
        Date now = new Date();
        if (seckillGoods1 == null || now.before(seckillGoods1.getStartTime()) || now.after(seckillGoods1.getEndTime()) || seckillGoods1.getStockCount() <= 0){
            return null;
        }
        addSeckillGoodsToRedis(seckillGoods1);
        return seckillGoods1;
    }

    /**
     * 给降级的请求一个体面的响应
     * @param goodsId
     * @param e
     * @return
     */
    public SeckillGoods mySqlBlockHandler(Long goodsId, BlockException e){
        System.out.println("服务降级处理");
        return null;
    }
}
